use crate::configuration;
use crate::extractor::TypedHeader;
use crate::header::{System, SystemType, Token};
use crate::rediss::RedisJson;
use crate::{ApiError, Auth, ToError, rediss};
use axum::body::Body;
use axum::extract::FromRequestParts;
use axum::http::Request;
use axum::response::Response;
use log::info;
use matchit::Router;
use redis::{AsyncCommands, IntegerReplyOrNoOp};
use serde::{Deserialize, Serialize};
use std::pin::Pin;
use std::sync::LazyLock;
use tower_http::auth::{AsyncAuthorizeRequest, AsyncRequireAuthorizationLayer};

static AUTH_LAYER: LazyLock<AsyncRequireAuthorizationLayer<JWTAuth>> =
    LazyLock::new(|| AsyncRequireAuthorizationLayer::new(JWTAuth));

#[derive(Clone)]
pub struct JWTAuth;

impl AsyncAuthorizeRequest<Body> for JWTAuth {
    type RequestBody = Body;
    type ResponseBody = Body;
    type Future = Pin<
        Box<
            dyn Future<Output = Result<Request<Self::RequestBody>, Response<Self::ResponseBody>>>
                + Send
                + 'static,
        >,
    >;

    fn authorize(&mut self, mut request: Request<Body>) -> Self::Future {
        Box::pin(async move {
            // 会去掉了嵌套路由的前缀
            let path = request.uri().path();
            let white_list = white_list();
            let matched = white_list.at(path);
            if let Ok(_) = matched {
                info!("白名单放行, 路径: {path}");
                return Ok(request);
            }
            let (mut parts, body) = request.into_parts();

            let TypedHeader(System(system_type)) =
                TypedHeader::<System>::from_request_parts(&mut parts, &()).await?;

            let TypedHeader(Token(token)) =
                TypedHeader::<Token>::from_request_parts(&mut parts, &()).await?;

            let mut connect = rediss::get().await?;
            let _: RedisJson<Principal> = connect
                .get(&token)
                .await
                .to_err(|| ApiError::Authorization(Auth::NoToken))?;
            let expire: IntegerReplyOrNoOp = connect
                .ttl(&token)
                .await
                .to_err(|| ApiError::Authorization(Auth::NoToken))?;
            if let IntegerReplyOrNoOp::IntegerReply(ttl) = expire {
                let expire = match system_type {
                    SystemType::Sys => configuration::auth().sys_expire(),
                    _ => configuration::auth().app_expire(),
                };

                if ttl as u64 <= expire / 2 {
                    let _: bool = connect
                        .expire(&token, expire as i64)
                        .await
                        .to_err(|| ApiError::Authorization(Auth::NoToken))?;
                }
            }
            request = Request::from_parts(parts, body);

            // 将用户信息保存在扩展中，方便在handler中使用
            // request.extensions_mut().insert(principal);
            Ok(request)
        })
    }
}

pub fn get_auth_layer() -> &'static AsyncRequireAuthorizationLayer<JWTAuth> {
    &AUTH_LAYER
}

static WHITE_LIST: LazyLock<Router<()>> = LazyLock::new(|| {
    let mut m = Router::new();
    let white_list = configuration::auth().white_list();
    white_list
        .iter()
        .for_each(|path| m.insert(path, ()).expect("添加白名单错误"));
    m
});

pub fn white_list() -> &'static Router<()> {
    &WHITE_LIST
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Principal {
    /// 用户id
    pub id: i64,
    /// 用户名
    pub name: String,
}
